/* @flow */

import { makeMap, isBuiltInTag, cached, no } from 'shared/util'

let isStaticKey
let isPlatformReservedTag

const genStaticKeysCached = cached(genStaticKeys)

/**
 * Goal of the optimizer: walk the generated template AST tree
 * and detect sub-trees that are purely static, i.e. parts of
 * the DOM that never needs to change.
 *
 * Once we detect these sub-trees, we can:
 *
 * 1. Hoist them into constants, so that we no longer need to
 *    create fresh nodes for them on each re-render;
 * 2. Completely skip them in the patching process.
 *
 * 优化静态内容。顾名思义，Vue中对于生成的ast会做优化，静态内容是指和数据没有关系，
 * 不需要每次都刷新的内容，这一步主要就是找出ast中的静态内容，并加以标注。
 */
export function optimize (root: ?ASTElement, options: CompilerOptions) {
  if (!root) return
  //staticKeys为"staticClass,staticStyle"
  isStaticKey = genStaticKeysCached(options.staticKeys || '')
  isPlatformReservedTag = options.isReservedTag || no
  // first pass: mark all non-static nodes.
  markStatic(root)
  // second pass: mark static roots.
  // root肯定不可使用v-for指令，因为一个组件只能有一个根节点
  markStaticRoots(root, false)
}

function genStaticKeys (keys: string): Function {
  return makeMap(
    'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
    (keys ? ',' + keys : '')
  )
}

function markStatic (node: ASTNode) {
  node.static = isStatic(node)
  if (node.type === 1) {
    // do not make component slot content static. this avoids
    // 1. components not able to mutate slot nodes
    // 2. static slot content fails for hot-reloading
    if (
      //!isPlatformReservedTag(node.tag)判断说明node不为html原生的元素，而可能是Vue提供或者自定义的组件，如上的英文描述，
      //除非该组件为slot或者该元素存在inline-template属性，否则该组件内的内部元素不会标记为static。
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) { //在if判断为true时，说明node.static = false，isPlatformReservedTag(node.tag)为false时，isStatic会返回false，
        //也就是说，对于vue组件，除了slot组件，其他组件的static属性总为false
      return
    }

    //代码可以运行到这里，说明node节点为：
    // 1）该节点为slot
    // 2）该节点上存在inline-template属性
    // 3）该节点是html提供的原生元素
    for (let i = 0, l = node.children.length; i < l; i++) {
      const child = node.children[i]
      markStatic(child)
      if (!child.static) {
        //只要当前node存在子孙元素不为static，则该node也不为static
        node.static = false
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        const block = node.ifConditions[i].block
        markStatic(block)
        if (!block.static) {
          node.static = false
        }
      }
    }
  }
}

function markStaticRoots (node: ASTNode, isInFor: boolean) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor
    }
    // For a node to qualify as a static root, it should have children that
    // are not just static text. Otherwise the cost of hoisting out will
    // outweigh the benefits and it's better off to just always render it fresh.
    //对于一个静态根结点，它不应该只包含静态文本，否则消耗会超过获得的收益，更好的做法让它每次渲染时都刷新
    if (node.static && node.children.length && !(
      node.children.length === 1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true
      return
    } else {
      node.staticRoot = false
    }
    if (node.children) {
      for (let i = 0, l = node.children.length; i < l; i++) {
        //isInFor说明祖先节点是否存在v-for指令，!!node.for说明当前节点是否存在v-for指令，
        //但是注意了，markStaticRoots的第一个参数为node.children[i]，也就是node节点的
        //子节点，所以isInFor为true的情况是node节点的祖先节点存在v-for指令，但是不包括
        //当前节点
        markStaticRoots(node.children[i], isInFor || !!node.for)
      }
    }
    if (node.ifConditions) {
      for (let i = 1, l = node.ifConditions.length; i < l; i++) {
        markStaticRoots(node.ifConditions[i].block, isInFor)
      }
    }
  }
}

//静态节点的情况有：
//1）节点是文本节点
//2）通过v-pre修饰
//3）参考return语句中的条件判断
function isStatic (node: ASTNode): boolean {
  if (node.type === 2) { // expression
    return false
  }
  if (node.type === 3) { // text
    return true
  }

  return !!(node.pre || (
    !node.hasBindings && // no dynamic bindings
    !node.if && !node.for && // not v-if or v-for or v-else
    !isBuiltInTag(node.tag) && // not a built-in 指slot和component组件
    isPlatformReservedTag(node.tag) && // not a component 如果是vue组件，则元素不为static
    !isDirectChildOfTemplateFor(node) &&
    //node节点的ASTNode的所有属性必须全部在isStaticKey中存在，
    //只要node节点的ASTNode的属性中存在isStaticKey不存在的属性，就返回false
    Object.keys(node).every(isStaticKey)
  ))
}

//只有在node元素的祖先元素是template元素，并且该祖先元素存在v-for指令，则返回true，
//只要查找到某祖先元素不为template元素，立即返回false
function isDirectChildOfTemplateFor (node: ASTElement): boolean {
  while (node.parent) {
    node = node.parent
    //只要祖先元素不为template，则立即返回false
    if (node.tag !== 'template') {
      return false
    }

    //如果代码运行到该位置，则说明当前node节点为template
    if (node.for) {
      //如果当前node节点既是template，而且该template节点上存在v-for指令，则返回true
      return true
    }
  }
  return false
}
